﻿// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. 
// See License.txt in the project root for license information.
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading.Actors;
using System.Threading.Actors.Helpers;
using System.Collections.Generic;
using System.Threading;

namespace TestActorMethods
{
    [TestClass]
    public class EmbeddedListUnitTests
    {
        [TestMethod]
        public void EmbeddedList_Test()
        {
            Console.WriteLine("Testing EmbeddedList.");
            IActorState list = ActorUnitTests.CreateActorState();
            string listName = "TestList";

            EmbeddedList elist = new EmbeddedList(list, listName, false);
            Assert.AreEqual(1, elist.Add("a"));
            Assert.AreEqual(2, elist.Add("b"));
            Assert.AreEqual(3, elist.Add("c"));

            Assert.IsTrue(elist.Contains("a"));
            Assert.IsTrue(elist.Contains("b"));
            Assert.IsTrue(elist.Contains("c"));
            Assert.IsFalse(elist.Contains("d"));

            Assert.AreEqual(3, elist.Count);

            Assert.AreEqual(0, elist.IndexOf("a"));
            Assert.AreEqual(1, elist.IndexOf("b"));
            Assert.AreEqual(2, elist.IndexOf("c"));

            object[] copy = new object[3];
            elist.CopyTo(copy);
            Assert.AreEqual(copy[0], "a");
            Assert.AreEqual(copy[1], "b");
            Assert.AreEqual(copy[2], "c");

            elist.RemoveAt(1);
            Assert.AreEqual(2, elist.Count);
            Assert.AreEqual(0, elist.IndexOf("a"));
            Assert.AreEqual(1, elist.IndexOf("c"));

            elist.Remove("a");
            Assert.AreEqual(1, elist.Count);
            Assert.AreEqual(0, elist.IndexOf("c"));

            elist.Clear();
            Assert.AreEqual(0, elist.Count);

            elist.AddRange(copy);
            Assert.AreEqual(3, elist.Count);
            Assert.AreEqual(0, elist.IndexOf("a"));
            Assert.AreEqual(1, elist.IndexOf("b"));
            Assert.AreEqual(2, elist.IndexOf("c"));

            elist.RemoveRange(0, 2);
            Assert.AreEqual(1, elist.Count);
            Assert.AreEqual(0, elist.IndexOf("c"));

            elist.Insert(0, "a");
            elist.Insert(0, "b");
            elist.Insert(1, "d");
            Assert.AreEqual(4, elist.Count);

            Assert.AreEqual(0, elist.IndexOf("b"));
            Assert.AreEqual(1, elist.IndexOf("d"));
            Assert.AreEqual(2, elist.IndexOf("a"));
            Assert.AreEqual(3, elist.IndexOf("c"));

            elist.Sort();
            Assert.AreEqual(0, elist.IndexOf("a"));
            Assert.AreEqual(1, elist.IndexOf("b"));
            Assert.AreEqual(2, elist.IndexOf("c"));
            Assert.AreEqual(3, elist.IndexOf("d"));

            elist.Dispose();
        }

        [TestMethod]
        public void EmbeddedList_TestLifetime()
        {
            Console.WriteLine("Testing EmbeddedList lifetime.");
            IActorState list = ActorUnitTests.CreateActorState();
            string listName = "TestList";

            // create list directly
            ListHelperMethods.TryConstruct(list, new object[] {listName});
            ListHelperMethods.Push(list, new object[] { listName, "right", "X" });

            EmbeddedList elist2 = new EmbeddedList(list, listName, false);
            elist2 = null;

            // list still has element though wrapper is out of scope
            Assert.AreEqual(1, ListHelperMethods.Length(list, new object[] { listName }));

            EmbeddedList elist3 = new EmbeddedList(list, listName, false);
            // verify that wrapper sees same list
            Assert.AreEqual(1, elist3.Count);
            Assert.AreEqual(0, elist3.IndexOf("X"));

            // explict dispose should destroy list
            elist3.Dispose();

            // using the list directly should fail
            try
            {
                ListHelperMethods.Length(list, new object[] { listName });
            }
            catch (Exception ex)
            {
                Assert.AreEqual(ex.GetType(), typeof(InvalidOperationException));
            }
        }

        [TestMethod]
        public void EmbeddedList_Publish()
        {
            Console.WriteLine("Testing EmbeddedList publish settings.");
            IActorState list = ActorUnitTests.CreateActorState();
            string listName = "TestList";
            SubscribeTarget subscribeTarget = new SubscribeTarget();

            list.Subscribe(subscribeTarget, "*");

            // test with Publish enabled
            EmbeddedList elist = new EmbeddedList(list, listName, true);
            Assert.AreEqual(1, elist.Add("a"));
            Assert.AreEqual(2, elist.Add("b"));
            Assert.AreEqual(3, elist.Add("c"));

            elist.RemoveAt(2);
            elist.RemoveAt(1);
            elist.RemoveAt(0);

            Assert.AreEqual(1, elist.Add("a"));
            elist[0] = "b";
            elist.Clear();
            list.Unsubscribe(subscribeTarget, "*");

            // Wait a bit for the publications to percolate, then test that our various type counts are correct.
            Thread.Sleep(50);
            Assert.AreEqual(4, subscribeTarget.AddedCount);
            Assert.AreEqual(3, subscribeTarget.RemovedCount);
            Assert.AreEqual(1, subscribeTarget.ReplacedCount);
            Assert.AreEqual(1, subscribeTarget.ClearedCount);

            // test with Publish disabled
            // get new target
            subscribeTarget = new SubscribeTarget();
            elist = new EmbeddedList(list, listName);
            Assert.AreEqual(1, elist.Add("a"));
            Assert.AreEqual(2, elist.Add("b"));
            Assert.AreEqual(3, elist.Add("c"));

            elist.RemoveAt(2);
            elist.RemoveAt(1);
            elist.RemoveAt(0);

            Assert.AreEqual(1, elist.Add("a"));
            elist[0] = "b";
            elist.Clear();
            list.Unsubscribe(subscribeTarget, "*");

            // Wait for a bit for any potential publications to percolate, then check that counts are 0.
            Thread.Sleep(50);
            Assert.AreEqual(0, subscribeTarget.AddedCount);
            Assert.AreEqual(0, subscribeTarget.RemovedCount);
            Assert.AreEqual(0, subscribeTarget.ReplacedCount);
            Assert.AreEqual(0, subscribeTarget.ClearedCount);

        }

        class SubscribeTarget : IPublicationCallbackContract
        {
            public int AddedCount;
            public int ReplacedCount;
            public int RemovedCount;
            public int ClearedCount;

            public SubscribeTarget()
            {
                AddedCount = 0;
                ReplacedCount = 0;
                RemovedCount = 0;
                ClearedCount = 0;
            }

            public void OnNewEvent(ActorEvent actorEvent)
            {
                switch (actorEvent.EventType)
                {
                    case "Collection.ElementAdded":
                        AddedCount++;
                        break;
                    case "Collection.ElementRemoved":
                        RemovedCount++;
                        break;
                    case "Collection.Cleared":
                        ClearedCount++;
                        break;
                    case "Collection.ElementReplaced":
                        ReplacedCount++;
                        break;
                }
            }

            public void OnCompleting()
            {
            }
        }
    }
}
